#include <sstream>
#include <ifaddrs.h>
#include <netdb.h>

#include "address.h"
#include "log.h"
#include "endian.h"


namespace qtch{

static Logger::ptr logger = QTCH_LOG_NAME("system");

template<class T>
static T  CreateMask(uint32_t bits){
    if(sizeof(T) * 8 - bits < 0){
        QTCH_LOG_ERROR(logger) << "CreateMask(" << bits << ") error by bits < 0";
        throw std::logic_error("bits < 0");
    }
    return ~(((T)1 << (sizeof(T) * 8 - bits)) - 1);
}

template<class T>
static uint32_t CountBytes(T value){
    uint32_t result = 0;
    while(value){
        value &= value -1;
        result++;
    }
    return result;
}



Address::ptr Address::Create(const sockaddr* addr,socklen_t addrlen){
    if(addr == nullptr){
        return nullptr;
    }
    Address::ptr result;
    switch(addr->sa_family){
    case AF_INET:
        result.reset(new IPv4Address(*(sockaddr_in*)addr));
        break;
    case AF_INET6:
        result.reset(new IPv6Address(*(sockaddr_in6*)addr));
        break;
    default:
        result.reset(new UnknowAddress(*addr));
        break;
    }
    return result;
}

bool Address::Lookup(std::vector<Address::ptr>& result, const std::string& host,
        int family, int type , int protocol){
    addrinfo hints, *results, *next;
    hints.ai_flags = 0;
    hints.ai_family = family;
    hints.ai_protocol = protocol;
    hints.ai_socktype = type;
    hints.ai_addr = NULL;
    hints.ai_addrlen = 0;
    hints.ai_canonname = NULL;
    hints.ai_next = NULL;
    
    std::string node;
    const char* service = NULL;

    if(!host.empty() && host[0] == '['){
        const char* endipv6 = (const char *)memchr(host.c_str()+1,']', host.size()-1);
        if(endipv6){
            if(*(endipv6 + 1) == ':'){
                service = endipv6 + 2;
            }
            node = host.substr(1,endipv6 - host.c_str() - 1);
        }
    }
    if(node.empty()){
        service = (const char*)memchr(host.c_str(),':',host.size());
        if(service){
            if(!memchr(service+1,':',host.c_str()+host.size()-service -1)){
                node = host.substr(0,service - host.c_str());
                ++service;
            }
            // else{
            //     service = NULL;
            // }
        }
    }

    if(node.empty()){
        node = host;
    }
    int error = getaddrinfo(node.c_str(),service,&hints,&results);
    if(error){
        QTCH_LOG_DEBUG(logger) << "getaddrinfo("<< host <<", "
                               << family << ", "
                               << type << ", "
                               << protocol << ") rt="
                               << error << " errno= "
                               << errno << " strerr="
                               << strerror(errno);
        return false; 
    }
    next = results;
    while(next){
        result.push_back(Create(next->ai_addr,next->ai_addrlen));
        next = next->ai_next;
    }
    freeaddrinfo(results);
    return !result.empty();
}

Address::ptr Address::LookupAny(const std::string& host, 
        int family, int type, int protocol){
    std::vector<Address::ptr> result;
    if(Lookup(result,host,family,type,protocol)){
        return result[0];
    }
    return nullptr;
}

std::shared_ptr<IPAddress> Address::LookupAnyIPAdress(const std::string& host,
        int family, int type, int protocol){
    std::vector<Address::ptr> result;
    if(Lookup(result,host,family,type,protocol)){
        for(size_t i = 0;i<result.size(); ++i){
            IPAddress::ptr it = std::dynamic_pointer_cast<IPAddress>(result[i]);
            if(it){
                return it;
            }
        }
    }
    return nullptr;
}
bool Address::GetInterfaceAddress(std::multimap<std::string,std::pair<Address::ptr, uint32_t> >& result,
        int family){
    struct ifaddrs *next, *results;
    if(getifaddrs(&results)){
        QTCH_LOG_DEBUG(logger) << "Address::GetInterfaceAddress::getifaddrs faile "
                               << "errno=" << errno << " strerr=" << strerror(errno);
        return false;
    }
    
    try{
        for(next = results; next; next = next->ifa_next){
            Address::ptr addr;
            uint32_t prefix_len = 0;
            if(family != AF_UNSPEC && family != next->ifa_addr->sa_family){
                continue;
            }
            switch(next->ifa_addr->sa_family){
                case AF_INET:
                    {
                        addr = Create(next->ifa_addr,sizeof(next->ifa_addr));
                        uint32_t netmask = ((sockaddr_in*)next->ifa_netmask)->sin_addr.s_addr;
                        prefix_len = CountBytes(netmask);
                    }
                    break;
                case AF_INET6:
                    {
                        addr = Create(next->ifa_addr,sizeof(next->ifa_addr));
                        in6_addr& netmasks = ((sockaddr_in6*)next->ifa_netmask)->sin6_addr;
                        prefix_len = 0;
                        for(int i=0; i < 16; ++i){
                            prefix_len += CountBytes(netmasks.s6_addr[i]);
                        }
                    }
                    break;
                default:
                    break;
            }

            if(addr){
                result.insert(std::make_pair(next->ifa_name,std::make_pair(addr,prefix_len)));
            }
        }
    }catch(...){
        QTCH_LOG_ERROR(logger) << "Address::GetInterfaceAddresses exception";
        freeifaddrs(results);
        return false;
    }
    freeifaddrs(results);
    return !result.empty();

}

bool Address::GetInterfaceAddress(std::vector<std::pair<Address::ptr, uint32_t> >& result,
        const std::string& iface, int family){
    std::multimap<std::string
          ,std::pair<Address::ptr, uint32_t> > results;
    if(!GetInterfaceAddress(results,family)){
        return false;
    }
    auto its = results.equal_range(iface);
    for(;its.first !=  its.second; ++its.first){
        result.push_back(its.first->second);
    }
    return !result.empty();
}



int Address::getFamily() const{
    return getAddr()->sa_family;
}

std::string Address::toString() const{
    std::stringstream ss;
    insert(ss);
    return ss.str();
    
}

bool Address::operator<(const Address& rhs) const{
    socklen_t minlen = std::min(getAddrLen(), rhs.getAddrLen());
    int result = memcmp(getAddr(), rhs.getAddr(), minlen);
    if(result < 0){
        return true;
    }else if(result > 0){
        return false;
    }else if(getAddrLen() <rhs.getAddrLen()){
        return true;
    }else {
        return false;
    }
}

bool Address::operator==(const Address& rhs) const{
    return getAddrLen()==rhs.getAddrLen()
        && memcmp(getAddr(),rhs.getAddr(),getAddrLen())==0;
}

bool Address::operator!=(const Address& rhs) const{
    return !(*this == rhs);
}

IPAddress::ptr IPAddress::Create(const char* address, uint16_t port){
    addrinfo hints, *results;
    memset(&hints, 0, sizeof(addrinfo));

    hints.ai_flags = AI_NUMERICHOST;
    hints.ai_family = AF_UNSPEC;
    int rt = getaddrinfo(address,NULL,&hints,&results);
    if(rt){
        QTCH_LOG_ERROR(logger) << "IPAddress Create(" << address << ", "
            << port << ") faile errno=" << errno << " errstr=" << strerror(errno);
        return nullptr;
    }
    try{
        IPAddress::ptr ip_ptr = std::dynamic_pointer_cast<IPAddress>(
            Address::Create(results->ai_addr,results->ai_addrlen)
        );
        if(ip_ptr){
            ip_ptr->setPort(port);
        }
        freeaddrinfo(results);
        return ip_ptr;
        
    }catch(...){
        freeaddrinfo(results);
        return nullptr;
    }
}

IPv4Address::ptr IPv4Address::Create(const char* address, uint16_t port){
    IPv4Address::ptr rt(new IPv4Address());
    rt->m_addr.sin_port = byteswapToBigEndian(port);
    int result = inet_pton(AF_INET, address,&rt->m_addr.sin_addr);
    if(result <= 0){
        QTCH_LOG_ERROR(logger) << "IPv4Address::Create(" << address << ","
                << port << ") rt=" << result << "errno=" << errno
                << " errstr=" << strerror(errno);
        return nullptr; 
    }
    return rt;
}

IPv4Address::IPv4Address(const sockaddr_in& address){
    m_addr = address;
}

IPv4Address::IPv4Address(uint32_t address, uint16_t port){
    memset(&m_addr,0, sizeof(m_addr));
    m_addr.sin_family = AF_INET;
    m_addr.sin_port = byteswapToBigEndian(port);
    m_addr.sin_addr.s_addr = byteswapToBigEndian(address);
}

const sockaddr* IPv4Address::getAddr() const {
    return (sockaddr*)&m_addr;
}

sockaddr* IPv4Address::getAddr() {
    return (sockaddr*)&m_addr;
}

socklen_t IPv4Address::getAddrLen() const {
    return sizeof(m_addr);
}

std::ostream& IPv4Address::insert(std::ostream& os) const {
    uint32_t addr = byteswapToBigEndian(m_addr.sin_addr.s_addr);
    os << ((addr >> 24) & 0xff) << "."
       << ((addr >> 16) & 0xff) << "."
       << ((addr >> 8) & 0xff) << "."
       << (addr & 0xff) ;
    os << ":" << byteswapToBigEndian(m_addr.sin_port);
    return os;
}
IPAddress::ptr IPv4Address::broadcastAddress(uint32_t prefix_len) {
    if(prefix_len > 32){
        return nullptr;
    }

    sockaddr_in baddr(m_addr);
    baddr.sin_addr.s_addr |= byteswapToBigEndian(~CreateMask<uint32_t>(prefix_len));
    return IPv4Address::ptr(new IPv4Address(baddr));
}

IPAddress::ptr IPv4Address::networkAddress(uint32_t prefix_len) {
    if(prefix_len > 32){
        return nullptr;
    }
    sockaddr_in baddr(m_addr);
    baddr.sin_addr.s_addr &= byteswapToBigEndian(CreateMask<uint32_t>(prefix_len));
    return IPv4Address::ptr(new IPv4Address(baddr));
}

IPAddress::ptr IPv4Address::subnetMask(uint32_t prefix_len) {
    if(prefix_len > 32){
        return nullptr;
    }
    sockaddr_in baddr(m_addr);
    baddr.sin_addr.s_addr = byteswapToBigEndian(CreateMask<uint32_t>(prefix_len));
    return IPv4Address::ptr(new IPv4Address(baddr));
}

uint16_t IPv4Address::getPort() const {
    return byteswapToBigEndian(m_addr.sin_port);
}

void IPv4Address::setPort(uint16_t v) {
    m_addr.sin_port = byteswapToBigEndian(v);
}


IPv6Address::ptr IPv6Address::Create(const char* address, uint16_t port){
    IPv6Address::ptr rt(new IPv6Address());
    rt->m_addr.sin6_port = byteswapToBigEndian(port);
    int result = inet_pton(AF_INET6, address,&rt->m_addr.sin6_addr);
    if(result <= 0){
        QTCH_LOG_ERROR(logger) << "IPv6Address::Create(" << address << ","
                << port << ") rt=" << result << "errno=" << errno
                << " errstr=" << strerror(errno);
        return nullptr; 
    }
    return rt;
}

IPv6Address::IPv6Address(){
    memset(&m_addr,0,sizeof(m_addr));
    m_addr.sin6_family = AF_INET6;
}

IPv6Address::IPv6Address(const sockaddr_in6& address){
    m_addr = address;
}

IPv6Address::IPv6Address(const uint8_t address[16], uint16_t port){
    memset(&m_addr,0,sizeof(m_addr));
    m_addr.sin6_family = AF_INET6;
    m_addr.sin6_port = byteswapToBigEndian(port);
    memcpy(m_addr.sin6_addr.s6_addr,address,16);
}

const sockaddr* IPv6Address::getAddr() const {
    return (sockaddr*)&m_addr;
}

sockaddr* IPv6Address::getAddr() {
    return (sockaddr*)&m_addr;
}

socklen_t IPv6Address::getAddrLen() const {
    return sizeof(m_addr);
}

std::ostream& IPv6Address::insert(std::ostream& os) const {
    os << "[";
    uint16_t* addr = (uint16_t*)m_addr.sin6_addr.s6_addr;
    bool use_zero = false;
    for(size_t i = 0; i < 8; ++i){
        if(addr[i] == 0 && !use_zero){
            continue;
        }
        if(i && addr[i-1] == 0 && !use_zero){
            os << ":";
            use_zero = true;
        }
        if(i){
            os << ":";
        }
        os << std::hex << (int)byteswapToBigEndian(addr[i]) << std::dec;
    }
    if(!use_zero && addr[7]==0){
        os << "::";
    }
    os << "]:" << byteswapToBigEndian(m_addr.sin6_port);
    return os;
}

IPAddress::ptr IPv6Address::broadcastAddress(uint32_t prefix_len) {
    if(prefix_len > 128){
        return nullptr;
    }

    sockaddr_in6 baddr(m_addr);
    baddr.sin6_addr.s6_addr[prefix_len / 8] |= ~CreateMask<uint8_t>(prefix_len % 8);
    for(size_t i = (prefix_len / 8) + 1; i <= 16; ++i){
        baddr.sin6_addr.s6_addr[i] = 0xff;
    }
    return IPv6Address::ptr(new IPv6Address(baddr));
}

IPAddress::ptr IPv6Address::networkAddress(uint32_t prefix_len) {
    if(prefix_len > 128){
        return nullptr;
    }

    sockaddr_in6 baddr(m_addr);
    baddr.sin6_addr.s6_addr[prefix_len / 8] &= CreateMask<uint8_t>(prefix_len % 8);
    for(size_t i = (prefix_len / 8) + 1; i <= 16; ++i){
        baddr.sin6_addr.s6_addr[i] = 0x00;
    }
    return IPv6Address::ptr(new IPv6Address(baddr));
}

IPAddress::ptr IPv6Address::subnetMask(uint32_t prefix_len) {
    if(prefix_len > 128){
        return nullptr;
    }

    sockaddr_in6 baddr(m_addr);
    memset(&baddr.sin6_addr.s6_addr,0, sizeof(baddr.sin6_addr.s6_addr));
    baddr.sin6_addr.s6_addr[prefix_len / 8] = CreateMask<uint8_t>(prefix_len % 8);
    for(size_t i = 0; i < (prefix_len / 8); ++i){
        baddr.sin6_addr.s6_addr[i] = 0xff;
    }
    return IPv6Address::ptr(new IPv6Address(baddr));
}

uint16_t IPv6Address::getPort() const {
    return byteswapToBigEndian(m_addr.sin6_port);
}

void IPv6Address::setPort(uint16_t v) {
    m_addr.sin6_port = byteswapToBigEndian(v);
}

static const size_t MAX_PATH_LEN = sizeof(((sockaddr_un*)0)->sun_path) - 1;

UnixAddress::UnixAddress(){
    memset(&m_addr,0,sizeof(m_addr));
    m_addr.sun_family = AF_UNIX;
    m_length = offsetof(sockaddr_un,sun_path) + MAX_PATH_LEN;
}

UnixAddress::UnixAddress(const std::string& path){
    memset(&m_addr,0,sizeof(m_addr));
    m_addr.sun_family = AF_UNIX;
    m_length = path.size() + 1;
    if(!path.empty()&&path[0] == '\0'){
        --m_length;
    }
    if(m_length > sizeof(m_addr.sun_path)){
        throw std::logic_error("path too long");
    }
    memcpy(m_addr.sun_path,path.c_str(), m_length);
    m_length += offsetof(sockaddr_un,sun_path);
}

std::string UnixAddress::getPath() const{
    std::stringstream ss;
    if(m_length > offsetof(sockaddr_un,sun_path) && m_addr.sun_path[0] == '\0'){
        ss << "\\0" << std::string(m_addr.sun_path + 1, m_length - offsetof(sockaddr_un,sun_path)-1);
    }else{
        ss << m_addr.sun_path;
    }
    return ss.str();
}

void UnixAddress::setAddrLen(uint32_t v){
    m_length = v;
}

const sockaddr* UnixAddress::getAddr() const {
    return (sockaddr*)&m_addr;
}

sockaddr* UnixAddress::getAddr() {
    return (sockaddr*)&m_addr;
}

socklen_t UnixAddress::getAddrLen() const {
    return m_length;
}

std::ostream& UnixAddress::insert(std::ostream& os) const {
    if(m_length > offsetof(sockaddr_un,sun_path) && m_addr.sun_path[0] == '\0'){
        os << "\\0" << std::string(m_addr.sun_path + 1, m_length - offsetof(sockaddr_un,sun_path)-1);
    }else{
        os << m_addr.sun_path;
    }
    return os;
}

UnknowAddress::UnknowAddress(int family){
    memset(&m_addr,0,sizeof(m_addr));
    m_addr.sa_family = family;
}

UnknowAddress::UnknowAddress(const sockaddr& addr){
    m_addr = addr;
}

const sockaddr* UnknowAddress::getAddr() const {
    return (sockaddr*)&m_addr;
}

sockaddr* UnknowAddress::getAddr() {
    return (sockaddr*)&m_addr;
}

socklen_t UnknowAddress::getAddrLen() const {
    return sizeof(m_addr);
}

std::ostream& UnknowAddress::insert(std::ostream& os) const {
    os << "[UnknowAddress family=" << m_addr.sa_family << "]";
    return os;
}

std::ostream& operator<<(std::ostream& os, const Address& addr){
    return addr.insert(os);
}

#define XX(name) \
std::ostream& operator<<(std::ostream& os, const name addr){ \
    return addr->insert(os);\
}

XX(Address::ptr);
XX(IPv4Address::ptr);
XX(IPv6Address::ptr);
XX(UnixAddress::ptr);
XX(UnknowAddress::ptr);
XX(IPAddress::ptr);
#undef XX

}